/*
 * @Authoryour name
 * @Date2020-12-03 23:40:09
 * @LastEditTime2020-12-03 23:40:09
 * @LastEditorsyour name
 * @DescriptionIn User Settings Edit
 * @FilePath\STM32H750_FreeRTOS\User\application\backtrace\backtrack.c
 */ 
#include "backtrace.h"
#include "schedule.h"
#include <stdio.h>

/* stack and code address and size variables definition */
static uint32_t main_stack_start_addr = 0;
static uint32_t main_stack_size = 0;
static uint32_t code_start_addr = 0;
static uint32_t code_size = 0;

/* function */
static void backtrace_dump_stack(uint32_t start_addr, uint32_t stack_size, uint32_t sp);
static void backtrace_get_stack_info(uint32_t *start_addr, uint32_t *size);
static boolean backtrace_is_incodesection(uint32_t pc);
static boolean backtrace_is_bl_blx_inst(uint32_t addr);

/**
 * @brief  Backtrace initialize, obtain the code/main_statck(not task stack) start address
           and size
 * @param  void
 * @retval void
 */
void backtrace_init(void)
{
    main_stack_start_addr = (uint32_t)&CSTACK_BLOCK_START(CMB_CSTACK_BLOCK_NAME);
    main_stack_size = (uint32_t)&CSTACK_BLOCK_END(CMB_CSTACK_BLOCK_NAME) - main_stack_start_addr;
    code_start_addr = (uint32_t)&CODE_SECTION_START(CMB_CODE_SECTION_NAME);
    code_size = (uint32_t)&CODE_SECTION_END(CMB_CODE_SECTION_NAME) - code_start_addr;
}

/**
 * @brief  Call this function to print current call stack information
 * @param  sp, the stack pointer
 * @retval void
 */
void backtrace_assert(uint32_t sp)
{
    uint32_t start_addr, stack_size, pc, depth = 0;
    uint32_t call_stack_buffer[BACKTRACE_CALL_NUM];
    backtrace_get_stack_info(&start_addr, &stack_size);

    for( ; sp < start_addr + stack_size; sp += sizeof(uint32_t))
    {
        /* 1. if sp is lr value, should minus sizeof(uint32_t) to obtain pc 
           2. pc should be a odd number */
        pc = *((uint32_t *)sp) - sizeof(uint32_t);
        if((pc & 1ul) == 0)
            continue;
        /*  */
        pc = *((uint32_t *)sp) - 1;
        if(backtrace_is_incodesection(pc) && backtrace_is_bl_blx_inst(pc - sizeof(uint32_t)) \
            && (depth < BACKTRACE_CALL_NUM))
        {
            call_stack_buffer[depth] = pc;
            depth++;
        }
    }
    printf("The call stack is: \r\n");
    for(uint32_t index = 0; index < depth; ++index)
    {
        printf("#%d : 0x%08x\r\n", index, call_stack_buffer[index]);
        
    }

    backtrace_dump_stack(start_addr, stack_size, sp);

    printf("The task stack pointer is       : 0x%08x\r\n", sp);
    printf("The task stack start address is : 0x%08x\r\n", start_addr);
    printf("The task stack size is          : 0x%08x\r\n", stack_size);
    printf("The main stack start address is : 0x%08x\r\n", main_stack_start_addr);
    printf("The main stack size is          : 0x%08x\r\n", main_stack_size);
    printf("The code start address is       : 0x%08x\r\n", code_start_addr);
    printf("The code size is                : 0x%08x\r\n", code_size);
}

/**
 * @brief  Print the dump stack information
 * @param  start_addr, the stack start address
 * @param  stack_size, the stack size
 * @param  sp, stack pointer
 * @retval void
 */
static void backtrace_dump_stack(uint32_t start_addr, uint32_t stack_size, uint32_t sp)
{
    uint32_t line_index, pos_index;
    uint32_t line_num, remain_num = 0;
    
    line_num = stack_size >> 2;
    remain_num = stack_size - (line_num << 2);
    printf("Stack dump information:\r\n");
    for(line_index = 0; line_index < line_num; ++line_index)
    {
        printf("0x%08x : ", start_addr + (line_index << 2));
        for(pos_index = 0; pos_index < 4; ++pos_index)
        {
            printf("0x%08x ", *((uint32_t *)(start_addr + (line_index << 2) + pos_index)));
        }
        printf("\r\n");
    }

    if(remain_num != 0)
    {
        printf("0x%08x : ", start_addr + line_index << 2 + 1);
        for(pos_index = 0; pos_index < remain_num; ++pos_index)
        {
            printf("0x%08x ", *((uint32_t *)(start_addr + (line_index << 2) + 1 + pos_index)));
        }
        printf("\r\n");
    }

}

/**
 * @brief  Get the current task stack information
 * @param  start_addr, [out], the stack start address
 * @param  size, [out], the stack size
 * @retval void
 */
static void backtrace_get_stack_info(uint32_t *start_addr, uint32_t *size)
{
    // extern uint32_t ulTaskGetStartStackAddr(void);
    // extern uint16_t uTaskGetStackSize(void);
    // task_info_t *task_info = NULL;
    // *start_addr = ulTaskGetStartStackAddr();
    // task_info = schedule_find_task_info(*start_addr);
    // if(task_info == NULL)
    // {
    //     *size = 0;
    //     ExprAssertCalled(__FILE__, __LINE__);
    //     return;
    // }
    
    // *size = (uint32_t)task_info->stack_depth * sizeof(uint32_t);
}

/**
 * @brief  Judge weather the pc is in the code section
 * @param  pc, the program counter
 * @retval TRUE: in the code section
           FALSE: not in the code section
 */
static boolean backtrace_is_incodesection(uint32_t pc)
{
    return (pc >= code_start_addr && pc <= (code_start_addr + code_size));
}

/**
 * @brief  Judge weather the instruction in addr is BL or BLX
 * @param  addr, the address
 * @retval TRUE: is BL or BLX instruction
           FALSE: not BL or BLX instruction
 */
static boolean backtrace_is_bl_blx_inst(uint32_t addr)
{
    uint16_t ins1 = *((uint16_t *)addr);
    uint16_t ins2 = *((uint16_t *)(addr + 2));

#define BL_INS_MASK         0xF800
#define BL_INS_HIGH         0xF800
#define BL_INS_LOW          0xF000
#define BLX_INX_MASK        0xFF00
#define BLX_INX             0x4700

    if ((ins2 & BL_INS_MASK) == BL_INS_HIGH && (ins1 & BL_INS_MASK) == BL_INS_LOW) 
    {
        return TRUE;
    } 
    else if ((ins2 & BLX_INX_MASK) == BLX_INX) 
    {
        return TRUE;
    } 
    else 
    {
        return FALSE;
    }
}
